/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.util;

import java.io.FilterWriter;
import java.io.IOException;
import java.io.Writer;

/**
 * Writer that wraps another writer and passes width-limited and
 * optionally-prefixed output to its subordinate. When lines are wrapped they
 * are automatically indented based on the start of the line.
 */
public final class IndentingWriter extends FilterWriter {

	/** {@code null-ok;} optional prefix for every line */
	private final String prefix;

	/** {@code > 0;} the maximum output width */
	private final int width;

	/** {@code > 0;} the maximum indent */
	private final int maxIndent;

	/** {@code >= 0;} current output column (zero-based) */
	private int column;

	/** whether indent spaces are currently being collected */
	private boolean collectingIndent;

	/** {@code >= 0;} current indent amount */
	private int indent;

	/**
	 * Constructs an instance.
	 * 
	 * @param out
	 *            {@code non-null;} writer to send final output to
	 * @param width
	 *            {@code >= 0;} the maximum output width (not including
	 *            {@code prefix}), or {@code 0} for no maximum
	 * @param prefix
	 *            {@code non-null;} the prefix for each line
	 */
	public IndentingWriter(Writer out, int width, String prefix) {
		super(out);

		if (out == null) {
			throw new NullPointerException("out == null");
		}

		if (width < 0) {
			throw new IllegalArgumentException("width < 0");
		}

		if (prefix == null) {
			throw new NullPointerException("prefix == null");
		}

		this.width = (width != 0) ? width : Integer.MAX_VALUE;
		this.maxIndent = width >> 1;
		this.prefix = (prefix.length() == 0) ? null : prefix;

		bol();
	}

	/**
	 * Constructs a no-prefix instance.
	 * 
	 * @param out
	 *            {@code non-null;} writer to send final output to
	 * @param width
	 *            {@code >= 0;} the maximum output width (not including
	 *            {@code prefix}), or {@code 0} for no maximum
	 */
	public IndentingWriter(Writer out, int width) {
		this(out, width, "");
	}

	/** {@inheritDoc} */
	@Override
	public void write(int c) throws IOException {
		synchronized (lock) {
			if (collectingIndent) {
				if (c == ' ') {
					indent++;
					if (indent >= maxIndent) {
						indent = maxIndent;
						collectingIndent = false;
					}
				} else {
					collectingIndent = false;
				}
			}

			if ((column == width) && (c != '\n')) {
				out.write('\n');
				column = 0;
				/*
				 * Note: No else, so this should fall through to the next if
				 * statement.
				 */
			}

			if (column == 0) {
				if (prefix != null) {
					out.write(prefix);
				}

				if (!collectingIndent) {
					for (int i = 0; i < indent; i++) {
						out.write(' ');
					}
					column = indent;
				}
			}

			out.write(c);

			if (c == '\n') {
				bol();
			} else {
				column++;
			}
		}
	}

	/** {@inheritDoc} */
	@Override
	public void write(char[] cbuf, int off, int len) throws IOException {
		synchronized (lock) {
			while (len > 0) {
				write(cbuf[off]);
				off++;
				len--;
			}
		}
	}

	/** {@inheritDoc} */
	@Override
	public void write(String str, int off, int len) throws IOException {
		synchronized (lock) {
			while (len > 0) {
				write(str.charAt(off));
				off++;
				len--;
			}
		}
	}

	/**
	 * Indicates that output is at the beginning of a line.
	 */
	private void bol() {
		column = 0;
		collectingIndent = (maxIndent != 0);
		indent = 0;
	}
}
